#include "AudioIn.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include "Header.h"
#include "Mutex.h"
#include "ThreadPool.h"
#include "ErrorManager.h"
#include "Error.h"

using namespace AgoraClient;

AudioIn::AudioIn(IBuffer *buf, unsigned int ALBufferSize) :
  _alBufferSize(ALBufferSize),
  _isRunning(false),
  _infoAudioSpacial(NULL),
  _voiceActivityDetection(false)
{
  this->_deviceList = NULL;
  this->_deviceTable = NULL;
  this->_currentDevice = NULL;
  this->_inRun = false;
  this->_sleepReactivity = 10;
  this->_buf = buf;
  this->_samples = new char[_alBufferSize];
  _mutex = new Mutex();
}

AudioIn::~AudioIn()
{
  this->Stop();
#ifdef _WIN32
  Sleep(200);  // TODO URGENT ... bug de synchro dans les delete depuis l'export C
#else
  usleep(1000);
#endif
  delete _mutex;
  delete this->_deviceList;
  delete[] this->_deviceTable;
  delete[] _samples;
  if (_currentDevice != NULL)
    {
      ALCboolean ok = alcCaptureCloseDevice(_currentDevice);
      if (ok == ALC_FALSE)
	throw "error";
		
    }
}

void AudioIn::SetVoiceActivityDetection(bool value, unsigned short levelVoiceActivity, unsigned short timeNoCheckAfterDetection)
{
	_levelVoiceActivity = levelVoiceActivity;
	_timeNoCheckAfterDetection = timeNoCheckAfterDetection;
	_voiceActivityDetection = value;
}

char **AudioIn::DetectDevice()
{
	if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
	{
		int numDevice = 0;
		char *ptr;
		if (this->_deviceList != NULL)
		{
			delete this->_deviceList;
		}
		this->_deviceList = (char*)alcGetString
		(
			NULL, 
			ALC_ALL_DEVICES_SPECIFIER
		);
		if (this->_deviceList == "")
		{
			return (char **)-1;
		}
		ptr = this->_deviceList;
		while( ptr && *ptr )
		{
			while (*ptr != NULL)
				++ptr;
			ptr++;
			numDevice++;
		}
		if (this->_deviceTable != NULL)
		{
			delete [] this->_deviceTable;
		}
		this->_deviceTable = new char *[numDevice];
		ptr = this->_deviceList;
		numDevice = 0;
		while( ptr && *ptr )
		{
			this->_deviceTable[numDevice] = ptr;
			while (*ptr != NULL)
				++ptr;
			ptr++;
			numDevice++;
		}
		this->_deviceTable[numDevice] = NULL;
	}
	else
	{
		return NULL;
	}
	return this->_deviceTable;
}

void AudioIn::SelectInDevice(char *deviceName, unsigned short frequence, char format)
{
	ALenum _format;

	if (format == STEREO16)
		_format = AL_FORMAT_STEREO16;
	else if  (format == STEREO8)
		_format = AL_FORMAT_STEREO8;
	else if  (format == MONO16)
		_format = AL_FORMAT_MONO16;
	else
		_format = AL_FORMAT_MONO8;
	this->_freq = frequence;
	this->_format = format;
	if (this->_currentDevice != NULL)
	{
		Stop();
		alcCaptureCloseDevice(this->_currentDevice);
		this->_currentDevice = NULL;
	}
	_sampleSize = (format == STEREO16 ? 4 : (format != MONO8 ? 2 : 1));
	this->_currentDevice = alcCaptureOpenDevice
	(
		deviceName,
		frequence,
		_format,
		_alMaxSample = this->_alBufferSize / _sampleSize
	);

	if (_inRun)
		Start();
}

void AudioIn::SelectInDevice(int deviceId, unsigned short frequence, char format)
{
	ALenum _format;

	if (format == STEREO16)
		_format = AL_FORMAT_STEREO16;
	else if  (format == STEREO8)
		_format = AL_FORMAT_STEREO8;
	else if  (format == MONO16)
		_format = AL_FORMAT_MONO16;
	else
		_format = AL_FORMAT_MONO8;
	this->_freq = frequence;
	this->_format = format;
	if (this->_currentDevice != NULL)
	{
		Stop();
		alcCaptureCloseDevice(this->_currentDevice);
		this->_currentDevice = NULL;
	}
	_sampleSize = (format == STEREO16 ? 4 : (format != MONO8 ? 2 : 1));
	this->_currentDevice = alcCaptureOpenDevice(
		this->_deviceTable[deviceId],
		frequence,
		_format,
		_alMaxSample = this->_alBufferSize / _sampleSize
		);
	if (_inRun)
		Start();
}

void AudioIn::AutoSelectInDevice(unsigned short frequence, char format)
{
	ALenum _format;

	if (format == STEREO16)
		_format = AL_FORMAT_STEREO16;
	else if  (format == STEREO8)
		_format = AL_FORMAT_STEREO8;
	else if  (format == MONO16)
		_format = AL_FORMAT_MONO16;
	else
		_format = AL_FORMAT_MONO8;
	this->_freq = frequence;
	this->_format = format;
	if (this->_currentDevice != NULL)
	{
		Stop();
		alcCaptureCloseDevice(this->_currentDevice);
	}
	_sampleSize = (format == STEREO16 ? 4 : (format != MONO8 ? 2 : 1));
	this->_currentDevice = alcCaptureOpenDevice(
		NULL,
		frequence,
		_format,
		_alMaxSample = this->_alBufferSize /_sampleSize
		);

	if (_inRun)
		Start();
}

void AudioIn::Start()
{
	ThreadPool::Request *req;

	if (this->_inRun)
		return;
	this->_inRun = true;
	req = new ThreadPool::Request();

	req->autoFree = true;
	req->parameters = this;
	req->function = (ThreadPool::callback)AudioIn::runIn;
	ThreadPool::GetThreadPool()->AddRequest(req);
}

void AudioIn::runIn(AudioIn *audioIn)
{
	static int count = 0;
	std::vector<ALshort>::iterator	it;
	std::vector<ALshort>::iterator	end;

	audioIn->_mutex->Lock();
	audioIn->_isRunning = true;
	if (!audioIn->_currentDevice)
	{
		ErrorManager::GetErrorManager()->AddError(new Error("No audio input device"));
		return;
	}
	alcCaptureStart(audioIn->_currentDevice);
	
	while (audioIn->_inRun && audioIn->_currentDevice)
	{
		ALCint samplesAvailable;

		alcGetIntegerv
		(
			audioIn->_currentDevice, 
			ALC_CAPTURE_SAMPLES, 
			1,
			&samplesAvailable
		);

		if (samplesAvailable > 0)		
		{
			samplesAvailable = samplesAvailable < audioIn->_alMaxSample ? samplesAvailable : audioIn->_alMaxSample;
			alcCaptureSamples
			(
				audioIn->_currentDevice, 
				audioIn->_samples,
				samplesAvailable
			);
			if (audioIn->_voiceActivityDetection)
			{
				short *data = (short*)audioIn->_samples;
				short tmp =  data[0];
				bool clean = count == 0;
				if (count > 0)
					count--;
				for (unsigned int i = 0; i < (samplesAvailable * audioIn->_sampleSize) / sizeof(short); ++i)
				{
					if ((tmp - data[i]) > audioIn->_levelVoiceActivity)
					{
						count = audioIn->_timeNoCheckAfterDetection / audioIn->_sleepReactivity;
						clean = false;
					}
					tmp = data[i];
					
				}
				if (!clean)
				{
					audioIn->_buf->Write(audioIn->_samples, samplesAvailable * audioIn->_sampleSize);
				}
			}
			else
				audioIn->_buf->Write(audioIn->_samples, samplesAvailable * audioIn->_sampleSize);
		}
		#ifdef _WIN32
		Sleep(audioIn->_sleepReactivity);
		#else
		usleep(audioIn->_sleepReactivity);
		#endif
	}
	alcCaptureStop(audioIn->_currentDevice);
	audioIn->_isRunning = false;
	
	audioIn->_mutex->Unlock();
}


void AudioIn::Stop()
{
	if (!_isRunning)
		return;
	this->_inRun = false;
	this->_mutex->Lock();
	this->_mutex->Unlock();
}

unsigned short AudioIn::GetFrequence()
{
	return this->_freq;
}

char AudioIn::GetFormat()
{
	return this->_format;
}

IBuffer *AudioIn::GetBuffer()
{
	return this->_buf;
}

bool AudioIn::IsSpacial()
{
	return this->_infoAudioSpacial != NULL;
}
	
const AgoraCore::InfoAudioSpacial& AudioIn::GetAudioSpacialInfo()
{
	return *(this->_infoAudioSpacial);
}

void AudioIn::SetSpacial(bool value)
{
	if (value && this->_infoAudioSpacial == NULL)
	{
		this->_infoAudioSpacial = new AgoraCore::InfoAudioSpacial();
		this->_infoAudioSpacial->x = 0;
		this->_infoAudioSpacial->y = 0;
		this->_infoAudioSpacial->z = 0;
		this->_infoAudioSpacial->vx = 0;
		this->_infoAudioSpacial->vy = 0;
		this->_infoAudioSpacial->vz = 0;
		this->_infoAudioSpacial->dirx = 0;
		this->_infoAudioSpacial->diry = 0;
		this->_infoAudioSpacial->dirz = 0;
	}
	else if (!value && this->_infoAudioSpacial != NULL)
	{
		delete this->_infoAudioSpacial;
		this->_infoAudioSpacial = NULL;
	}
}

void AudioIn::SetSpacialInfo(int x, int y, int z, 
							 int velocityX, int velocityY, int velocityZ,
							 int dirX, int dirY, int dirZ)
{
	this->SetSpacial(true);
	this->_infoAudioSpacial->x = x;
	this->_infoAudioSpacial->y = y;
	this->_infoAudioSpacial->z = z;
	this->_infoAudioSpacial->vx = velocityX;
	this->_infoAudioSpacial->vy = velocityY;
	this->_infoAudioSpacial->vz = velocityZ;
	this->_infoAudioSpacial->dirx = dirX;
	this->_infoAudioSpacial->diry = dirY;
	this->_infoAudioSpacial->dirz = dirZ;
}